/***************************************************************
 *
 * (c) 2008 Markus Dittrich 
 *
 * This program is free software; you can redistribute it 
 * and/or modify it under the terms of the GNU General Public 
 * License Version 3 as published by the Free Software Foundation. 
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License Version 3 for more details.
 *
 * You should have received a copy of the GNU General Public 
 * License along with this program; if not, write to the Free 
 * Software Foundation, Inc., 59 Temple Place - Suite 330, 
 * Boston, MA 02111-1307, USA.
 *
 ****************************************************************/

/* stdlib includes */
#include <cmath>
#include <float.h>

/* local includes */
#include "basic_definitions.h"
#include "tick_label_manager.h"


/* pull some declarations into namespace */
using std::string;
using std::vector;



//---------------------------------------------------------------
// constructor 
//---------------------------------------------------------------
TickLabelManager::TickLabelManager() 
  :
    Epsilon_(1e-10)
{
  /* NOTE: this is silly! Unfortunately, I don't know how
   * to initialize a boost::array in the constructor initializer
   * list. */
  Intervals_[0] = 0.1;
  Intervals_[1] = 0.2; 
  Intervals_[2] = 0.25; 
  Intervals_[3] = 0.4;
  Intervals_[4] = 0.5;
  Intervals_[5] = 0.75;
  Intervals_[6] = 1.0;
}



/*****************************************************************
 *
 * public member functions
 *
 *****************************************************************/

//----------------------------------------------------------------
// initialize class with a new set of minmax values
// 
// NOTE: TicklabelManager has to be called with a non-zero data
//       range! 
//----------------------------------------------------------------
void TickLabelManager::initialize(double minData, double maxData)
{
  Min_ = minData;
  Max_ = maxData;
  Diff_ = Max_ - Min_;
  Interval_ = 0.0;
  Divisor_ = 0;

  /* TicklabelManager has to be called with a non-zero data
   * range! */
  assert ( fabs(Diff_) > DBL_EPSILON );

  /* reset storage arrays */
  Labels_.clear();
  TickLocations_.clear();

  compute_tick_values_();
  generate_tick_labels_();
}

  
//----------------------------------------------------------------
// return list of position for the x axis labels
//----------------------------------------------------------------
const vector<double>& TickLabelManager::get_tick_values() const
{
  return TickLocations_;
} 


//----------------------------------------------------------------
// return list of labels corresponding to ticks
//----------------------------------------------------------------
const vector<QString>& TickLabelManager::get_label_values() const
{
    return Labels_;
} 



/*****************************************************************
 *
 * private member functions
 *
 *****************************************************************/

//----------------------------------------------------------------
// calculates the "best" number and location of x ticks.
// NOTE: divisor only returns the number of ticks between
// Min_ and Max_ and we have to check the enpoints separately
// if they match a potential tick location.
//----------------------------------------------------------------
void TickLabelManager::compute_tick_values_()
{
  FOREACH(double searchInterval, Intervals_)
  {
    Divisor_ = compute_divisor_(searchInterval);
    if ( Divisor_ < 10 ) 
    {
      Interval_ = searchInterval;
      break;
     }
  }
    
  check_interval_boundaries_for_ticks_();

  /* compute all tick locations */
  double startTickPosition = compute_start_tick_position_();
  for (int tickCounter = 0; tickCounter < Divisor_; ++tickCounter)
  {
    double currentLocation = startTickPosition + 
      tickCounter * compute_diff_magnitude_() * Interval_;
    TickLocations_.push_back(currentLocation);
  }
} 


//---------------------------------------------------------------
// check for additional ticks in case either Min_
// or Max_ are on a valid tick location. 
//---------------------------------------------------------------
void TickLabelManager::check_interval_boundaries_for_ticks_()
{
  /* check if Min_ matches a valid tick location */
  double potentialStartTick = compute_diff_magnitude_() * Interval_;
  if ( fabs(potentialStartTick - Min_) < Epsilon_ ) 
  {
    ++Divisor_;
  }


  /* test if their is another possible tick at the end of the
   * interval */
  int lastTick = Divisor_ - 1;
  int nextTick = lastTick + 1;
  double potentialEndTick = (compute_start_tick_position_() 
      + (compute_diff_magnitude_() * Interval_ * nextTick));
  if ( potentialEndTick <= Max_ )    
  {
    ++Divisor_;
  }
  else if ( fabs(potentialEndTick - Max_) < Epsilon_ )
  {
    ++Divisor_;
  }
}
 

//--------------------------------------------------------------
// compute the first tick location larger than Min_ that 
// is in accord with the chosen Interval_
//--------------------------------------------------------------
double TickLabelManager::compute_start_tick_position_()
{ 
  double position = 
    ceil(Min_/(compute_diff_magnitude_() * Interval_)) 
        * Interval_ * compute_diff_magnitude_();
  return position;
}


//--------------------------------------------------------------
// generates the labels corresponding to ticks
//--------------------------------------------------------------
void TickLabelManager::generate_tick_labels_()
{
  /* we make our formatting decisions based on 
   * abs(max(tickValues) */
  double maxTick = *std::max_element(TickLocations_.begin(), 
      TickLocations_.end());
  maxTick = fabs(maxTick);

  int fieldWidth = 4;
  char format = 'f';
  if ( maxTick <= 1e-4 || maxTick >= 1e4 )
  {
    fieldWidth = 2;
    format = 'e';
  }
  else if ( maxTick > 100 )
  {
    fieldWidth = 2;
  }
  else if ( maxTick > 10 )
  {
    fieldWidth = 3;
  }
  
  int paddingRight = -6;
  FOREACH(double tick, TickLocations_)
  {
    Labels_.push_back(QString("%1").arg(tick,paddingRight,format,
	  fieldWidth,'0'));
  }
}


//----------------------------------------------------------------
// function computing how often a given interval fits into
// the range of data given by Diff
//----------------------------------------------------------------
int TickLabelManager::compute_divisor_(double interval)
{
  assert ( fabs(interval) > DBL_EPSILON );
  assert ( fabs(Diff_) > DBL_EPSILON );

  int divisor = static_cast<int>
    (floor(Diff_/(compute_diff_magnitude_() * interval)));
  return divisor;
}


//----------------------------------------------------------------
// compute 10^n, where Diff_ = a * 10^n
//----------------------------------------------------------------
double TickLabelManager::compute_diff_magnitude_()
{
  double magnitude = pow(10,compute_diff_exponent_());
  return magnitude;
}


//-----------------------------------------------------------------
// compute n, where Diff_ = a * 10^n
//-----------------------------------------------------------------
double TickLabelManager::compute_diff_exponent_()
{
  assert ( fabs(Diff_) > DBL_EPSILON );
  
  double xDiffPowerTen = floor(log(Diff_)/log(10.0));
  return xDiffPowerTen;
}
